/*
 * LTE Game Engine
 * Copyright (C) 2006-2008 SiberianSTAR <haxormail@gmail.com>
 * http://www.ltestudios.com
 *  
 * The LTE Game Engine is based on Irrlicht 1.0
 * Irrlicht Engine is Copyright (C) 2002-2006 Nikolaus Gebhardt
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
 
// Copyright (C) 2002-2006 Nikolaus Gebhardt/Thomas Alten
// This file is part of the "engine Engine".
// For conditions of distribution and use, see copyright notice in engine.h

#include "CImage.h"
#include "os.h"
#include <stdio.h>
#include "SoftwareDriver2_helper.h"

namespace engine
{
struct AbsRectangle
{
	s32 x0;
	s32 y0;
	s32 x1;
	s32 y1;
};

inline s32 s32_clamp (s32 value, s32 low, s32 high)
{
	return s32_min (s32_max(value,low), high);
}

inline void intersect ( AbsRectangle &dest, const AbsRectangle& a, const AbsRectangle& b)
{
	dest.x0 = s32_max( a.x0, b.x0 );
	dest.y0 = s32_max( a.y0, b.y0 );
	dest.x1 = s32_min( a.x1, b.x1 );
	dest.y1 = s32_min( a.y1, b.y1 );
}

inline bool isValid (const AbsRectangle& a)
{
	return a.x0 < a.x1 && a.y0 < a.y1;
}


struct SBlitJob
{
	AbsRectangle Dest;
	AbsRectangle Source;

	u32 argb;

	void * src;
	void * dst;

	s32 width;
	s32 height;

	u32 srcPitch;
	u32 dstPitch;

	u32 srcPixelMul;
	u32 dstPixelMul;
};

// Blitter Operation
enum eBlitter
{
	BLITTER_INVALID = 0,
	BLITTER_COLOR,
	BLITTER_COLOR_ALPHA,
	BLITTER_TEXTURE,
	BLITTER_TEXTURE_ALPHA_BLEND,
	BLITTER_TEXTURE_ALPHA_COLOR_BLEND
};

typedef void (*tExecuteBlit)( const SBlitJob * job );


// Bitfields Cohen Sutherland
enum eClipCode
{
	CLIPCODE_EMPTY  =       0,
	CLIPCODE_BOTTOM =       1,
	CLIPCODE_TOP    =       2,
	CLIPCODE_LEFT   =       4,
	CLIPCODE_RIGHT  =       8
};

inline u32 GetClipCode ( const AbsRectangle &r, const core::position2d<s32> &p )
{
	u32 code = CLIPCODE_EMPTY;

	if ( p.X < r.x0 )
		code = CLIPCODE_LEFT;
	else
	if ( p.X > r.x1 )
		code = CLIPCODE_RIGHT;

	if ( p.Y < r.y0 )
		code |= CLIPCODE_TOP;
	else
	if ( p.Y > r.y1 )
		code |= CLIPCODE_BOTTOM;

	return code;
}


/*!
   	Cohen Sutherland clipping
   	@return: 1 if valid
 */

int ClipLine (  const AbsRectangle &clipping,
                core::position2d<s32> &p0,
                core::position2d<s32> &p1,
                const core::position2d<s32>& p0_in,
                const core::position2d<s32>& p1_in
                )
{
	u32 code0;
	u32 code1;
	u32 code;

	p0 = p0_in;
	p1 = p1_in;

	code0 = GetClipCode ( clipping, p0 );
	code1 = GetClipCode ( clipping, p1 );

	// trivial accepted
	while ( code0 | code1 )
	{
		s32 x=0;
		s32 y=0;

		// trivial reject
		if ( code0 & code1 )
			return 0;

		if ( code0 )
		{
			// clip first point
			code = code0;
		}
		else
		{
			// clip last point
			code = code1;
		}

		if ( (code & CLIPCODE_BOTTOM) == CLIPCODE_BOTTOM )
		{
			// clip bottom viewport
			y = clipping.y1;
			x = p0.X + ( p1.X - p0.X ) * ( y - p0.Y ) / ( p1.Y - p0.Y );
		}
		else
		if ( (code & CLIPCODE_TOP) == CLIPCODE_TOP )
		{
			// clip to viewport
			y = clipping.y0;
			x = p0.X + ( p1.X - p0.X ) * ( y - p0.Y ) / ( p1.Y - p0.Y );
		}
		else
		if ( (code & CLIPCODE_RIGHT) == CLIPCODE_RIGHT )
		{
			// clip right viewport
			x = clipping.x1;
			y = p0.Y + ( p1.Y - p0.Y ) * ( x - p0.X ) / ( p1.X - p0.X );
		}
		else
		if ( (code & CLIPCODE_LEFT) == CLIPCODE_LEFT )
		{
			// clip left viewport
			x = clipping.x0;
			y = p0.Y + ( p1.Y - p0.Y ) * ( x - p0.X ) / ( p1.X - p0.X );
		}

		if ( code == code0 )
		{
			// modify first point
			p0.X = x;
			p0.Y = y;
			code0 = GetClipCode ( clipping, p0 );
		}
		else
		{
			// modify second point
			p1.X = x;
			p1.Y = y;
			code1 = GetClipCode ( clipping, p1 );
		}
	}

	return 1;

}

/*
 */
inline void GetClip ( AbsRectangle &clipping, video::IImage * t)
{
	clipping.x0 = 0;
	clipping.y0 = 0;
	clipping.x1 = t->getDimension().Width - 1;
	clipping.y1 = t->getDimension().Height - 1;
}

/*
 */
void RenderLine32_Decal (       video::IImage *t,
                                const core::position2d<s32> &p0,
                                const core::position2d<s32> &p1,
                                u32 argb )
{
	s32 dx = p1.X - p0.X;
	s32 dy = p1.Y - p0.Y;

	s32 c;
	s32 m;
	s32 d = 0;
	s32 run;

	s32 xInc = 4;
	s32 yInc = (s32) t->getPitch();

	if ( dx < 0 )
	{
		xInc = -xInc;
		dx = -dx;
	}

	if ( dy < 0 )
	{
		yInc = -yInc;

		dy = -dy;
	}

	u32 *dst;
	dst = (u32*) ( (u8*) t->lock() + ( p0.Y * t->getPitch() ) + ( p0.X << 2 ) );

	if ( dy > dx )
	{
		swap_xor ( dx, dy );
		swap_xor ( xInc, yInc );
	}

	c = dx << 1;
	m = dy << 1;

	run = dx;
	while ( run )
	{
		*dst = argb;

		dst = (u32*) ( (u8*) dst + xInc );      // x += xInc
		d += m;
		if ( d > dx )
		{
			dst = (u32*) ( (u8*) dst + yInc );      // y += yInc
			d -= c;
		}
		run -= 1;
	}

	t->unlock();
}


/*
 */
void RenderLine32_Blend (       video::IImage *t,
                                const core::position2d<s32> &p0,
                                const core::position2d<s32> &p1,
                                u32 argb,
                                u32 alpha
                                )
{
	s32 dx = p1.X - p0.X;
	s32 dy = p1.Y - p0.Y;

	s32 c;
	s32 m;
	s32 d = 0;
	s32 run;

	s32 xInc = 4;
	s32 yInc = (s32) t->getPitch();

	if ( dx < 0 )
	{
		xInc = -xInc;
		dx = -dx;
	}

	if ( dy < 0 )
	{
		yInc = -yInc;

		dy = -dy;
	}

	u32 *dst;
	dst = (u32*) ( (u8*) t->lock() + ( p0.Y * t->getPitch() ) + ( p0.X << 2 ) );

	if ( dy > dx )
	{
		swap_xor ( dx, dy );
		swap_xor ( xInc, yInc );
	}

	c = dx << 1;
	m = dy << 1;

	run = dx;
	while ( run )
	{
		*dst = PixelBlend32 ( *dst, argb, alpha );

		dst = (u32*) ( (u8*) dst + xInc );      // x += xInc
		d += m;
		if ( d > dx )
		{
			dst = (u32*) ( (u8*) dst + yInc );      // y += yInc
			d -= c;
		}
		run -= 1;
	}

	t->unlock();
}

/*
 */
void RenderLine16_Decal (       video::IImage *t,
                                const core::position2d<s32> &p0,
                                const core::position2d<s32> &p1,
                                u32 argb )
{
	s32 dx = p1.X - p0.X;
	s32 dy = p1.Y - p0.Y;

	s32 c;
	s32 m;
	s32 d = 0;
	s32 run;

	s32 xInc = 2;
	s32 yInc = (s32) t->getPitch();

	if ( dx < 0 )
	{
		xInc = -xInc;
		dx = -dx;
	}

	if ( dy < 0 )
	{
		yInc = -yInc;

		dy = -dy;
	}

	u16 *dst;
	dst = (u16*) ( (u8*) t->lock() + ( p0.Y * t->getPitch() ) + ( p0.X << 1 ) );

	if ( dy > dx )
	{
		swap_xor ( dx, dy );
		swap_xor ( xInc, yInc );
	}

	c = dx << 1;
	m = dy << 1;

	run = dx;
	while ( run )
	{
		*dst = argb;

		dst = (u16*) ( (u8*) dst + xInc );      // x += xInc
		d += m;
		if ( d > dx )
		{
			dst = (u16*) ( (u8*) dst + yInc );      // y += yInc
			d -= c;
		}
		run -= 1;
	}

	t->unlock();
}

/*
 */
void RenderLine16_Blend (       video::IImage *t,
                                const core::position2d<s32> &p0,
                                const core::position2d<s32> &p1,
                                u32 argb,
                                u32 alpha
                                )
{
	s32 dx = p1.X - p0.X;
	s32 dy = p1.Y - p0.Y;

	s32 c;
	s32 m;
	s32 d = 0;
	s32 run;

	s32 xInc = 2;
	s32 yInc = (s32) t->getPitch();

	if ( dx < 0 )
	{
		xInc = -xInc;
		dx = -dx;
	}

	if ( dy < 0 )
	{
		yInc = -yInc;

		dy = -dy;
	}

	u16 *dst;
	dst = (u16*) ( (u8*) t->lock() + ( p0.Y * t->getPitch() ) + ( p0.X << 1 ) );

	if ( dy > dx )
	{
		swap_xor ( dx, dy );
		swap_xor ( xInc, yInc );
	}

	c = dx << 1;
	m = dy << 1;

	run = dx;
	while ( run )
	{
		*dst = PixelBlend16 ( *dst, argb, alpha );

		dst = (u16*) ( (u8*) dst + xInc );      // x += xInc
		d += m;
		if ( d > dx )
		{
			dst = (u16*) ( (u8*) dst + yInc );      // y += yInc
			d -= c;
		}
		run -= 1;
	}

	t->unlock();
}


/*!
 */
void executeBlit_TextureCopy_x_to_x ( const SBlitJob * job )
{
	void *src = (void*) job->src;
	void *dst = (void*) job->dst;

	const u32 widthPitch = job->width * job->dstPixelMul;
	for ( s32 dy = 0; dy != job->height; ++dy )
	{
		memcpy ( dst, src, widthPitch );

		src = (void*) ( (u8*) (src) + job->srcPitch );
		dst = (void*) ( (u8*) (dst) + job->dstPitch );
	}
}

/*!
 */
void executeBlit_TextureCopy_32_to_16 ( const SBlitJob * job )
{
	u32 *src = (u32*) job->src;
	u16 *dst = (u16*) job->dst;

	for ( s32 dy = 0; dy != job->height; ++dy )
	{
		for ( s32 dx = 0; dx != job->width; ++dx )
		{
			dst[dx] = video::A8R8G8B8toA1R5G5B5 ( src[dx] );
		}

		src = (u32*) ( (u8*) (src) + job->srcPitch );
		dst = (u16*) ( (u8*) (dst) + job->dstPitch );
	}
}

/*!
 */
void executeBlit_TextureCopy_24_to_16 ( const SBlitJob * job )
{
	void *src = (void*) job->src;
	u16 *dst = (u16*) job->dst;

	for ( s32 dy = 0; dy != job->height; ++dy )
	{
		u8 * s = (u8*) src;

		for ( s32 dx = 0; dx != job->width; ++dx )
		{
			dst[dx] = video::RGB16(s[0], s[1], s[2]);
			s += 3;
		}

		src = (void*) ( (u8*) (src) + job->srcPitch );
		dst = (u16*) ( (u8*) (dst) + job->dstPitch );
	}
}


/*!
 */
void executeBlit_TextureCopy_16_to_32 ( const SBlitJob * job )
{
	u16 *src = (u16*) job->src;
	u32 *dst = (u32*) job->dst;

	for ( s32 dy = 0; dy != job->height; ++dy )
	{
		for ( s32 dx = 0; dx != job->width; ++dx )
		{
			dst[dx] = video::A1R5G5B5toA8R8G8B8 ( src[dx] );
		}

		src = (u16*) ( (u8*) (src) + job->srcPitch );
		dst = (u32*) ( (u8*) (dst) + job->dstPitch );
	}
}

/*!
 */
void executeBlit_TextureCopy_24_to_32 ( const SBlitJob * job )
{
	void *src = (void*) job->src;
	u32 *dst = (u32*) job->dst;

	for ( s32 dy = 0; dy != job->height; ++dy )
	{
		u8 * s = (u8*) src;

		for ( s32 dx = 0; dx != job->width; ++dx )
		{
			dst[dx] = s[0] << 16 | s[1] << 8 | s[2];
			s += 3;
		}

		src = (void*) ( (u8*) (src) + job->srcPitch );
		dst = (u32*) ( (u8*) (dst) + job->dstPitch );
	}
}


/*!
 */
void executeBlit_TextureBlend_16_to_16 ( const SBlitJob * job )
{
	u32 *src = (u32*) job->src;
	u32 *dst = (u32*) job->dst;

	u32 rdx = job->width >> 1;
	u32 off = job->width & 1 ? (job->width - 1 ) : 0;

	if ( 0 == off )
	{
		for ( s32 dy = 0; dy != job->height; ++dy )
		{
			for ( u32 dx = 0; dx != rdx; ++dx )
			{
				dst[dx] = PixelBlend16_simd ( dst[dx], src[dx] );
			}

			src = (u32*) ( (u8*) (src) + job->srcPitch );
			dst = (u32*) ( (u8*) (dst) + job->dstPitch );
		}

	}
	else
	{
		for ( s32 dy = 0; dy != job->height; ++dy )
		{
			for ( u32 dx = 0; dx != rdx; ++dx )
			{
				dst[dx] = PixelBlend16_simd ( dst[dx], src[dx] );
			}

			((u16*) dst)[off] = PixelBlend16 ( ((u16*) dst)[off], ((u16*) src)[off] );

			src = (u32*) ( (u8*) (src) + job->srcPitch );
			dst = (u32*) ( (u8*) (dst) + job->dstPitch );
		}

	}
}

/*!
 */
void executeBlit_TextureBlend_32_to_32 ( const SBlitJob * job )
{
	u32 *src = (u32*) job->src;
	u32 *dst = (u32*) job->dst;

	for ( s32 dy = 0; dy != job->height; ++dy )
	{
		for ( s32 dx = 0; dx != job->width; ++dx )
		{
			dst[dx] = PixelBlend32 ( dst[dx], src[dx] );
		}
		src = (u32*) ( (u8*) (src) + job->srcPitch );
		dst = (u32*) ( (u8*) (dst) + job->dstPitch );
	}
}

/*!
 */
void executeBlit_TextureBlendColor_16_to_16 ( const SBlitJob * job )
{
	u16 *src = (u16*) job->src;
	u16 *dst = (u16*) job->dst;

	u16 blend = video::A8R8G8B8toA1R5G5B5 ( job->argb );
	for ( s32 dy = 0; dy != job->height; ++dy )
	{
		for ( s32 dx = 0; dx != job->width; ++dx )
		{
			dst[dx] = PixelBlend16 ( dst[dx], PixelMul16 ( src[dx], blend ) );
		}
		src = (u16*) ( (u8*) (src) + job->srcPitch );
		dst = (u16*) ( (u8*) (dst) + job->dstPitch );
	}
}


/*!
 */
void executeBlit_TextureBlendColor_32_to_32 ( const SBlitJob * job )
{
	u32 *src = (u32*) job->src;
	u32 *dst = (u32*) job->dst;

	for ( s32 dy = 0; dy != job->height; ++dy )
	{
		for ( s32 dx = 0; dx != job->width; ++dx )
		{
			dst[dx] = PixelBlend32 ( dst[dx], PixelMul32 ( src[dx], job->argb ) );
		}
		src = (u32*) ( (u8*) (src) + job->srcPitch );
		dst = (u32*) ( (u8*) (dst) + job->dstPitch );
	}
}

/*!
 */
void executeBlit_Color_16_to_16 ( const SBlitJob * job )
{
	u16 *dst = (u16*) job->dst;

	u32 c0 = video::A8R8G8B8toA1R5G5B5 ( job->argb );
	u32 c = c0 | c0 << 16;

	if ( 0 == (job->srcPitch & 3 ) )
	{
		for ( s32 dy = 0; dy != job->height; ++dy )
		{
			memset32 ( dst, c, job->srcPitch );
			dst = (u16*) ( (u8*) (dst) + job->dstPitch );
		}
	}
	else
	{
		s32 dx = job->width - 1;

		for ( s32 dy = 0; dy != job->height; ++dy )
		{
			memset32 ( dst, c, job->srcPitch );
			dst[dx] = c0;
			dst = (u16*) ( (u8*) (dst) + job->dstPitch );
		}

	}
}

/*!
 */
void executeBlit_Color_32_to_32 ( const SBlitJob * job )
{
	u32 *dst = (u32*) job->dst;

	for ( s32 dy = 0; dy != job->height; ++dy )
	{
		memset32 ( dst, job->argb, job->srcPitch );
		dst = (u32*) ( (u8*) (dst) + job->dstPitch );
	}
}

/*!
 */
void executeBlit_ColorAlpha_16_to_16 ( const SBlitJob * job )
{
	u16 *dst = (u16*) job->dst;

	u32 alpha = extractAlpha ( job->argb ) >> 3;
	u32 src = video::A8R8G8B8toA1R5G5B5 ( job->argb );

	for ( s32 dy = 0; dy != job->height; ++dy )
	{
		for ( s32 dx = 0; dx != job->width; ++dx )
		{
			dst[dx] = PixelBlend16 ( dst[dx], src, alpha );
		}
		dst = (u16*) ( (u8*) (dst) + job->dstPitch );
	}
}

/*!
 */
void executeBlit_ColorAlpha_32_to_32 ( const SBlitJob * job )
{
	u32 *dst = (u32*) job->dst;

	u32 alpha = extractAlpha ( job->argb );
	u32 src = job->argb;

	for ( s32 dy = 0; dy != job->height; ++dy )
	{
		for ( s32 dx = 0; dx != job->width; ++dx )
		{
			dst[dx] = PixelBlend32 ( dst[dx], src, alpha );
		}
		dst = (u32*) ( (u8*) (dst) + job->dstPitch );
	}
}

/*!
 */
tExecuteBlit getBlitter ( eBlitter operation,video::IImage * dest,video::IImage * source )
{
	video::ECOLOR_FORMAT sourceFormat = (video::ECOLOR_FORMAT)-1;
	video::ECOLOR_FORMAT destFormat = (video::ECOLOR_FORMAT)-1;

	if ( source )
		sourceFormat = source->getColorFormat();
	if ( dest )
		destFormat = dest->getColorFormat();

	switch ( operation )
	{
	case BLITTER_TEXTURE:
	{
		if ( sourceFormat == destFormat )
			return executeBlit_TextureCopy_x_to_x;

		if ( destFormat == video::ECF_A1R5G5B5 && sourceFormat == video::ECF_A8R8G8B8 )
			return executeBlit_TextureCopy_32_to_16;

		if ( destFormat == video::ECF_A1R5G5B5 && sourceFormat == video::ECF_R8G8B8 )
			return executeBlit_TextureCopy_24_to_16;

		if ( destFormat == video::ECF_A8R8G8B8 && sourceFormat == video::ECF_A1R5G5B5 )
			return executeBlit_TextureCopy_16_to_32;

		if ( destFormat == video::ECF_A8R8G8B8 && sourceFormat == video::ECF_R8G8B8 )
			return executeBlit_TextureCopy_24_to_32;

	} break;

	case BLITTER_TEXTURE_ALPHA_BLEND:
	{
		if ( destFormat == video::ECF_A1R5G5B5 && sourceFormat == video::ECF_A1R5G5B5 )
			return executeBlit_TextureBlend_16_to_16;

		if ( destFormat == video::ECF_A8R8G8B8 && sourceFormat == video::ECF_A8R8G8B8 )
			return executeBlit_TextureBlend_32_to_32;

	} break;

	case BLITTER_TEXTURE_ALPHA_COLOR_BLEND:
	{
		if ( destFormat == video::ECF_A1R5G5B5 && sourceFormat == video::ECF_A1R5G5B5 )
			return executeBlit_TextureBlendColor_16_to_16;

		if ( destFormat == video::ECF_A8R8G8B8 && sourceFormat == video::ECF_A8R8G8B8 )
			return executeBlit_TextureBlendColor_32_to_32;
	} break;

	case BLITTER_COLOR:
	{
		if ( destFormat == video::ECF_A1R5G5B5 )
			return executeBlit_Color_16_to_16;

		if ( destFormat == video::ECF_A8R8G8B8 )
			return executeBlit_Color_32_to_32;
	} break;

	case BLITTER_COLOR_ALPHA:
	{
		if ( destFormat == video::ECF_A1R5G5B5 )
			return executeBlit_ColorAlpha_16_to_16;

		if ( destFormat == video::ECF_A8R8G8B8 )
			return executeBlit_ColorAlpha_32_to_32;

	} break;

	}

	char buf[64];
	sprintf ( buf, "Blit: %d %d->%d unsupported",operation,sourceFormat,destFormat );
	os::Printer::log (buf );
	return 0;

}



/*!
   	a generic 2D Blitter
 */
s32 Blit (      eBlitter operation,
                video::IImage * dest,
                const core::rect<s32> *destClipping,
                const core::position2d<s32> *destPos,
                video::IImage * source,
                const core::rect<s32> *sourceClipping,
                u32 argb
                )
{
	tExecuteBlit blitter = getBlitter ( operation, dest, source );
	if ( 0 == blitter )
	{
		return 0;
	}

	// Clipping
	AbsRectangle sourceClip;
	AbsRectangle destClip;
	AbsRectangle v;

	SBlitJob job;

	if ( sourceClipping )
	{
		sourceClip.x0 = sourceClipping->UpperLeftCorner.X;
		sourceClip.y0 = sourceClipping->UpperLeftCorner.Y;
		sourceClip.x1 = sourceClipping->LowerRightCorner.X;
		sourceClip.y1 = sourceClipping->LowerRightCorner.Y;
	}
	else
	{
		sourceClip.x0 = 0;
		sourceClip.y0 = 0;
		sourceClip.x1 = source ? source->getDimension().Width : 0;
		sourceClip.y1 = source ? source->getDimension().Height : 0;
	}

	if ( destClipping )
	{
		destClip.x0 = destClipping->UpperLeftCorner.X;
		destClip.y0 = destClipping->UpperLeftCorner.Y;
		destClip.x1 = destClipping->LowerRightCorner.X;
		destClip.y1 = destClipping->LowerRightCorner.Y;
	}
	else
	{
		destClip.x0 = 0;
		destClip.y0 = 0;
		destClip.x1 = dest ? dest->getDimension().Width : 0;
		destClip.y1 = dest ? dest->getDimension().Height : 0;
	}

	v.x0 = destPos ? destPos->X : 0;
	v.y0 = destPos ? destPos->Y : 0;
	v.x1 = v.x0 + ( sourceClip.x1 - sourceClip.x0 );
	v.y1 = v.y0 + ( sourceClip.y1 - sourceClip.y0 );

	intersect ( job.Dest, destClip, v );
	if ( !isValid ( job.Dest ) )
		return 0;

	job.width = job.Dest.x1 - job.Dest.x0;
	job.height = job.Dest.y1 - job.Dest.y0;


	job.Source.x0 = sourceClip.x0 + ( job.Dest.x0 - v.x0 );
	job.Source.x1 = job.Source.x0 + job.width;

	job.Source.y0 = sourceClip.y0 + ( job.Dest.y0 - v.y0 );
	job.Source.y1 = job.Source.y0 + job.height;

	job.argb = argb;

	if ( source )
	{
		job.srcPitch = source->getPitch();
		job.srcPixelMul = source->getBytesPerPixel();
		job.src = (void*) ( (u8*) source->lock() + ( job.Source.y0 * job.srcPitch ) + ( job.Source.x0 * job.srcPixelMul ) );
	}
	else
	{
		// use srcPitch for color operation on dest
		job.srcPitch = job.width * dest->getBytesPerPixel();
	}

	job.dstPitch = dest->getPitch();
	job.dstPixelMul = dest->getBytesPerPixel();
	job.dst = (void*) ( (u8*) dest->lock() + ( job.Dest.y0 * job.dstPitch ) + ( job.Dest.x0 * job.dstPixelMul ) );

	blitter ( &job );

	if ( source )
		source->unlock ();

	if ( dest )
		dest->unlock();

	return 1;
}

}

namespace engine
{
namespace video
{

//! constructor
CImage::CImage(ECOLOR_FORMAT format, const core::dimension2d<s32>& size)
	: Format(format), Size(size), Data(0)
{
	initData();
}


//! constructor
CImage::CImage(ECOLOR_FORMAT format, const core::dimension2d<s32>& size, void* data,
               bool ownForeignMemory)
	: Format(format), Size(size), Data(0)
{
	if (ownForeignMemory)
	{
		Data = (void*)0xbadf00d;
		initData();
		Data = data;
	}
	else
	{
		Data = 0;
		initData();
		memcpy(Data, data, Size.Height * Size.Width * BytesPerPixel);
	}
}





//! constructor
CImage::CImage(ECOLOR_FORMAT format, IImage* imageToCopy)
	: Format(format), Data(0)
{
	if (!imageToCopy)
		return;

	Size = imageToCopy->getDimension();
	initData();

	// now copy data from other image

	Blit ( BLITTER_TEXTURE, this, 0, 0, imageToCopy, 0,0 );
}



//! constructor
CImage::CImage(IImage* imageToCopy,     const core::position2d<s32>& pos,
               const core::dimension2d<s32>& size)
	: Data(0), Size(0,0)
{
	if (!imageToCopy)
		return;

	Format = imageToCopy->getColorFormat();
	Size = size;

	initData();

	core::rect<s32> sClip( pos.X, pos.Y, pos.X + size.Width,pos.Y + size.Height );
	Blit (  BLITTER_TEXTURE, this, 0, 0, imageToCopy, &sClip,0 );
}




//! assumes format and size has been set and creates the rest
void CImage::initData()
{
	BitsPerPixel = getBitsPerPixelFromFormat();
	BytesPerPixel = BitsPerPixel / 8;

	// Pitch should be aligned...
	Pitch = BytesPerPixel * Size.Width;

	if (!Data)
		Data = new s8[Size.Height * Pitch];
}


//! destructor
CImage::~CImage()
{
	delete [] (s8*)Data;
}



//! Lock function.
void* CImage::lock()
{
	return Data;
}



//! Unlock function.
void CImage::unlock()
{
}



//! Returns width and height of image data.
const core::dimension2d<s32>& CImage::getDimension()
{
	return Size;
}



//! Returns bits per pixel.
s32 CImage::getBitsPerPixel()
{
	return BitsPerPixel;
}


//! Returns bytes per pixel
s32 CImage::getBytesPerPixel()
{
	return BytesPerPixel;
}



//! Returns image data size in bytes
s32 CImage::getImageDataSizeInBytes()
{
	return Pitch * Size.Height;
}



//! Returns image data size in pixels
s32 CImage::getImageDataSizeInPixels()
{
	return Size.Width * Size.Height;
}



//! returns mask for red value of a pixel
u32 CImage::getRedMask()
{
	return RedMask;
}



//! returns mask for green value of a pixel
u32 CImage::getGreenMask()
{
	return GreenMask;
}



//! returns mask for blue value of a pixel
u32 CImage::getBlueMask()
{
	return BlueMask;
}



//! returns mask for alpha value of a pixel
u32 CImage::getAlphaMask()
{
	return AlphaMask;
}


s32 CImage::getBitsPerPixelFromFormat()
{
	switch(Format)
	{
	case ECF_A1R5G5B5:
		AlphaMask = 0x1<<15;
		RedMask = 0x1F<<10;
		GreenMask = 0x1F<<5;
		BlueMask = 0x1F;
		return 16;
	case ECF_R5G6B5:
		AlphaMask = 0x0;
		RedMask = 0x1F<<11;
		GreenMask = 0x3F<<5;
		BlueMask = 0x1F;
		return 16;
	case ECF_R8G8B8:
		AlphaMask = 0x0;
		RedMask = 0xFF<<16;
		GreenMask = 0xFF<<8;
		BlueMask = 0xFF;
		return 24;
	case ECF_A8R8G8B8:
		AlphaMask = 0xFF<<24;
		RedMask = 0xFF<<16;
		GreenMask = 0xFF<<8;
		BlueMask = 0xFF;
		return 32;
	}

	os::Printer::log("CImage: Unknown color format.", ELL_ERROR);
	return 0;
}


//! returns a pixel
SColor CImage::getPixel(s32 x, s32 y)
{
	u8* p ;
	if (x < 0 || y < 0 || x >= Size.Width || y >= Size.Height)
		return SColor(0);
	int u;
	switch(Format)
	{
	case ECF_A1R5G5B5:
		u = A1R5G5B5toA8R8G8B8(((s16*)Data)[y*Size.Width + x]);
		p = (u8*)&u;
		return SColor(p[3],p[0],p[1],p[2]);
	case ECF_R5G6B5:
		u = R5G6B5toA8R8G8B8(((s16*)Data)[y*Size.Width + x]);
		p = (u8*)&u;
		return SColor(p[3],p[0],p[1],p[2]);
	case ECF_A8R8G8B8:
		u = ((s32*)Data)[y*Size.Width + x];
		p = (u8*)&u;
		return SColor(p[3],p[0],p[1],p[2]);
	case ECF_R8G8B8:
	{
		p = &((u8*)Data)[(y*3)*Size.Width + (x*3)];
		/*
		   			    B    G   R    */
		return SColor(255,p[2],p[1],p[0]);
	}
	}

	return SColor(0);
}


//! returns the color format
ECOLOR_FORMAT CImage::getColorFormat()
{
	return Format;
}

//! draws a rectangle
void CImage::drawRectangle(const core::rect<s32>& rect, const SColor &color)
{

	Blit (  color.getAlpha() == 0xFF ? BLITTER_COLOR : BLITTER_COLOR_ALPHA,
	        this, 0, &rect.UpperLeftCorner, 0, &rect, color.color);
}


//! copies this surface into another
void CImage::copyTo(CImage* target, const core::position2d<s32>& pos)
{
	Blit (  BLITTER_TEXTURE, target, 0, &pos, this, 0, 0 );
}


//! copies this surface into another
void CImage::copyTo(CImage* target, const core::position2d<s32>& pos, const core::rect<s32>& sourceRect, const core::rect<s32>* clipRect)
{
	Blit (  BLITTER_TEXTURE, target, clipRect, &pos, this, &sourceRect, 0 );
}



//! copies this surface into another, using the alpha mask, an cliprect and a color to add with
void CImage::copyToWithAlpha(CImage* target, const core::position2d<s32>& pos, const core::rect<s32>& sourceRect, const SColor &color, const core::rect<s32>* clipRect)
{
	Blit (  BLITTER_TEXTURE_ALPHA_COLOR_BLEND, // color.color == 0xFFFFFFFF ? BLITTER_TEXTURE_ALPHA_BLEND: BLITTER_TEXTURE_ALPHA_COLOR_BLEND,
	        target, clipRect, &pos, this, &sourceRect, color.color );
}



//! draws a line from to
void CImage::drawLine(const core::position2d<s32>& from, const core::position2d<s32>& to, const SColor &color)
{
	AbsRectangle clip;
	GetClip ( clip, this );

	core::position2d<s32> p[2];

	if ( ClipLine ( clip, p[0], p[1], from, to ) )
	{
		u32 alpha = extractAlpha ( color.color );

		switch ( Format )
		{
		case ECF_A1R5G5B5:
			if ( alpha == 256 )
			{
				RenderLine16_Decal ( this, p[0], p[1], video::A8R8G8B8toA1R5G5B5 ( color.color ) );
			}
			else
			{
				RenderLine16_Blend ( this, p[0], p[1], video::A8R8G8B8toA1R5G5B5 ( color.color ), alpha >> 3 );
			}
			break;
		case ECF_A8R8G8B8:
			if ( alpha == 256 )
			{
				RenderLine32_Decal ( this, p[0], p[1], color.color );
			}
			else
			{
				RenderLine32_Blend ( this, p[0], p[1], color.color, alpha );
			}
			break;
		}
	}
}
//! copies this surface into another, scaling it to fit it.
void CImage::copyToScaling(CImage* target)
{
	if (Format != target->getColorFormat() )
	{
		os::Printer::log("Format not equal", ELL_ERROR);
		return;
	}

	// note: this is very very slow. (i didn't want to write a fast version.
	// but hopefully, nobody wants to scale surfaces every frame.

	core::dimension2d<s32> size = target->getDimension();

	if (!size.Width || !size.Height)
		return;


	f32 sourceXStep = (f32)Size.Width / (f32)size.Width;
	f32 sourceYStep = (f32)Size.Height / (f32)size.Height;
	f32 sy;
	s32 x;
	s32 y;

	switch ( Format )
	{
	case video::ECF_A1R5G5B5:
	case video::ECF_R5G6B5:
	{
		s16* nData = (s16*)target->lock();

		for ( x=0; x<size.Width; ++x)
		{
			sy = 0.0f;

			for ( y=0; y<size.Height; ++y)
			{
				nData[(s32)(y*size.Width + x)] = ((s16*)Data)[(s32)(((s32)sy)*Size.Width + x*sourceXStep)];
				sy+=sourceYStep;
			}
		}
	} break;

	case video::ECF_A8R8G8B8:
	{
		u32* nData = (u32*)target->lock();

		for ( x=0; x<size.Width; ++x)
		{
			sy = 0.0f;

			for ( y=0; y<size.Height; ++y)
			{
				int col1 = ((u32*)Data)[(s32)(((s32)sy)*Size.Width + x*sourceXStep)];
				int col2 = ((u32*)Data)[(s32)(((s32)sy)*Size.Width + x*sourceXStep + 1)];

				int r1 = (col1 & 0xFF), r2 = (col2 & 0xFF);
				int g1 = ((col1 & 0xFF00) >> 8), g2 = ((col2 & 0xFF00) >> 8);
				int b1 = ((col1 & 0xFF0000) >> 16), b2 = ((col2 & 0xFF0000) >> 16);
				int a1 = ((col1 & 0xFF000000) >> 24), a2 = ((col2 & 0xFF000000) >> 24);

				int out = (r1+r2)/2 + (((g1+g2)/2)<<8) + (((b1+b2)/2)<<16) + (((a1+a2)/2)<<24);

				nData[(u32)(y*size.Width + x)] = out;


				sy+=sourceYStep;
			}
		}
	} break;

	}

	target->unlock();
}


//! fills the surface with black or white
void CImage::fill(const SColor &color)
{
	u32 c;

	switch ( Format )
	{
	case ECF_A1R5G5B5:
		c = video::A8R8G8B8toA1R5G5B5 ( color.color );
		c |= c << 16;
		break;
	case ECF_A8R8G8B8:
		c = color.color;
		break;
	default:
		os::Printer::log("CImage::Format not supported", ELL_ERROR);
		return;
	}

	memset32 ( Data, c, getImageDataSizeInBytes () );
}

//! returns pitch of image
u32 CImage::getPitch()
{
	return Pitch;
}


} // end namespace video
} // end namespace engine
